#ifndef N_NVX2LOADER_H
#define N_NVX2LOADER_H
//------------------------------------------------------------------------------
/**
    @class nNvx2Loader

    Load a NVX2 mesh file into user provided vertex and index buffers.
    
    (C) 2003 RadonLabs GmbH
*/
#include "gfx2/nmeshloader.h"

//------------------------------------------------------------------------------
class nNvx2Loader : public nMeshLoader
{
public:
    /// constructor
    nNvx2Loader();
    /// destructor
    virtual ~nNvx2Loader();
    /// open file and read header data
    virtual bool Open(nFileServer2* fs);
    /// close the file
    virtual void Close();
    /// read vertex data
    virtual bool ReadVertices(void* buffer, int bufferSize);
    /// read index data
    virtual bool ReadIndices(void* buffer, int bufferSize);
    /// read edge data
    virtual bool ReadEdges(void* buffer, int bufferSize);
};

//------------------------------------------------------------------------------
/**
*/
inline
nNvx2Loader::nNvx2Loader()
{
    // empty
}

//------------------------------------------------------------------------------
/**
*/
inline
nNvx2Loader::~nNvx2Loader()
{
    // empty
}

//------------------------------------------------------------------------------
/**
*/
inline
bool
nNvx2Loader::Open(nFileServer2* fs)
{
    n_assert(!this->file);
    n_assert(fs);

    this->fileServer = fs;
    this->file = this->fileServer->NewFileObject();
    n_assert(this->file);

    // open the file
    if (!this->file->Open(this->filename.Get(), "rb"))
    {
        n_printf("nNvx2Loader: could not open file '%s'!\n", this->filename.Get());
        this->Close();
        return false;
    }

    // read file header, including groups
    int magic = file->GetInt();
    if (magic != 'NVX2')
    {
        n_printf("nNvx2Loader: '%s' is not a NVX2 file!\n", this->filename.Get());
        this->Close();
        return false;
    }
    this->numGroups        = file->GetInt();
    this->numVertices      = file->GetInt();
    this->fileVertexWidth  = file->GetInt();
    this->numTriangles     = file->GetInt();
    this->numEdges         = file->GetInt();
    this->fileVertexComponents = file->GetInt();
    this->numIndices       = this->numTriangles * 3;

    int groupIndex;
    for (groupIndex = 0; groupIndex < this->numGroups; groupIndex++)
    {
        int firstVertex   = file->GetInt();
        int numVertices   = file->GetInt();
        int firstTriangle = file->GetInt();
        int numTriangles  = file->GetInt();
        int firstEdge     = file->GetInt();
        int numEdges      = file->GetInt();

        nMeshGroup group;
        group.SetFirstVertex(firstVertex);
        group.SetNumVertices(numVertices);
        group.SetFirstIndex(firstTriangle * 3);
        group.SetNumIndices(numTriangles * 3);
        group.SetFirstEdge(firstEdge);
        group.SetNumEdges(numEdges);
        this->groupArray.Append(group);
    }

    return nMeshLoader::Open(fileServer);
}

//------------------------------------------------------------------------------
/**
*/
inline
void
nNvx2Loader::Close()
{
    if (this->file)
    {
        if (this->file->IsOpen())
        {
            this->file->Close();
        }
        this->file->Release();
        this->file = 0;
    }
}

//------------------------------------------------------------------------------
/**
*/
inline
bool
nNvx2Loader::ReadVertices(void* buffer, int bufferSize)
{
    n_assert(buffer);
    n_assert(this->file);
    n_assert((this->numVertices * this->vertexWidth * int(sizeof(float))) == bufferSize);
    if (this->vertexComponents == this->fileVertexComponents)
    {
        n_assert(this->vertexWidth == this->fileVertexWidth);
        file->Read(buffer, bufferSize);
    }
    else
    {
        float* destBuf = (float*) buffer;
        float* readBuffer = n_new_array(float, this->fileVertexWidth);
        const int readSize = int(sizeof(float)) * this->fileVertexWidth;
        int v = 0;
        for (v = 0; v < this->numVertices; v++)
        {
            float* vBuf = readBuffer;
            int numRead = file->Read(vBuf, readSize);
            n_assert(numRead == readSize);
            int mask;
            for (mask = 1; mask <= this->vertexComponents; mask <<= 1)
            {                
                int num = 0;
                switch (mask)
                {
                // float 2
                case nMesh2::Uv0:
                case nMesh2::Uv1:
                case nMesh2::Uv2:
                case nMesh2::Uv3:
                    num = 2;
                    break;

                // float 3
                case nMesh2::Coord:
                case nMesh2::Normal:
                case nMesh2::Tangent:
                case nMesh2::Binormal:
                    num = 3;
                    break;

                // float 4
                case nMesh2::Color:
                case nMesh2::Weights:
                case nMesh2::JIndices:
                    num = 4;
                    break;

                default:
                    n_error("Unknown vertex component in vertex component mask");
                    break;
                }

                n_assert(num > 0);
                if (this->vertexComponents & mask)
                {
                    // read
                    *(destBuf++) = *(vBuf++);
                    if (num >= 2)
                    {
                        *(destBuf++) = *(vBuf++);
                    }
                    if (num >= 3)
                    {
                        *(destBuf++) = *(vBuf++);
                    }
                    if (num >= 4)
                    {
                        *(destBuf++) = *(vBuf++);
                    }
                }
                else
                {
                    // skip
                    vBuf += num;
                }
            }
        }
        n_delete_array(readBuffer);
    }
    return true;    
}

//------------------------------------------------------------------------------
/**
*/
inline
bool
nNvx2Loader::ReadIndices(void* buffer, int bufferSize)
{
    n_assert(buffer);
    n_assert(this->file);
    if (Index16 == this->indexType)
    {
        // 16 bit indices: read index array directly
        n_assert((this->numIndices * int(sizeof(ushort))) == bufferSize);
        file->Read(buffer, bufferSize);
    }
    else
    {
        // 32 bit indices, read into 16 bit buffer, and expand
        n_assert((this->numIndices * int(sizeof(uint))) == bufferSize);

        // read 16 bit indices into tmp buffer
        int size16 = this->numIndices * sizeof(ushort);
        ushort* ptr16 = (ushort*) n_malloc(size16);
        n_assert(ptr16);
        file->Read(ptr16, size16);

        // expand to 32 bit indices
        uint* ptr32 = (uint*) buffer;
        int i;
        for (i = 0; i < this->numIndices; i++)
        {
            ptr32[i] = (uint) ptr16[i];
        }

        // release tmp buffer
        n_free(ptr16);
    }
    return true;
}

//------------------------------------------------------------------------------
/**
    A edge has the size of 4 * ushort, so you have to provide a buffer with the
    size numEdges * 4 * sizeof(ushort).
    The edge data is: ushort faceIndex1, faceIndex2, vertexIndex1, vertexIndex2;
    If a face Indicie is invalid (a border edge with only on face connected)
    the value is (ushort)nMeshBuilder::InvalidIndex ( == -1).
*/
inline
bool
nNvx2Loader::ReadEdges(void* buffer, int bufferSize)
{
    n_assert(buffer);
    n_assert(this->file);
    if (this->numEdges > 0)
    {
        n_assert((this->numEdges * 4 * sizeof(ushort)) == bufferSize);
        file->Read(buffer, bufferSize);
        return true;
    }
    return false;
}

//------------------------------------------------------------------------------
#endif
